﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
using Daphne.Modularity.UI;
using Microsoft.Practices.Unity;
using Daphne.Board2DMetro.Style;
using Daphne.SharedMetro;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml;
using Windows.Foundation;
using Windows.UI.Xaml.Media;
using Windows.UI;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Data;
using Daphne.GameShared;
using Daphne.Modularity.Storage;
using System.Windows.Input;

namespace Daphne.Board2DMetro
{
    /// <summary>
    /// Class representing 2D board 
    /// </summary>
    [DependencyControl]
    public class Board2DControl: ContentControl, IInitialization
    {
        // TO-DO: Debug - find better way to set image path from the application
        private const string _imagePath = "ms-appx:///AppData/Images/";

        /// <summary>
        /// Reference to the interface for entering user moves
        /// </summary>
        [Dependency]
        private IUserMoveEntering  UserMoveEntering
        { get; set; }

        /// <summary>
        /// Reference to the interface reading the state of gameboard
        /// </summary>
        [Dependency]
        private IGameBoard GameBoard
        { get; set; }

        /// <summary>
        /// Style for board
        /// </summary>
        private IStyle _style;

        /// <summary>
        /// Positioning panel
        /// </summary>
        private Canvas _uxPanel;

        /// <summary>
        /// Dependency Property signalling whether
        /// we are in edit mode or not
        /// </summary>
        public static DependencyProperty EditModeProperty;

        /// <summary>
        /// Command raised when the field is tapped
        /// </summary>
        public static DependencyProperty FieldTappedCommandProperty;

        #region Image Attributes

        private BitmapImage _board;
        private BitmapImage _boardReversed;
        private BitmapImage _whitePawn;
        private BitmapImage _blackPawn;
        private BitmapImage _whiteKing;
        private BitmapImage _blackKing;

        // TO-DO: find better way to deduce the size of board 2D
        private const double _boardSize = 1024d;

        #endregion

        #region ImageControls

        private Image _boardImageControl;
        private Dictionary<Image, Position> _figureMap;

        #endregion

        private bool _moveInProgress = false;
        private Dictionary<Border, Position> _markedFields;
        private Position _moveStartPos, _targetPos;
        private Image _movingImage;

        /// <summary>
        /// Register dependency properties
        /// </summary>
        static Board2DControl()
        {
            EditModeProperty = DependencyProperty.Register(
                "EditMode", typeof(bool), typeof(Board2DControl),
                new PropertyMetadata(false));

            FieldTappedCommandProperty = DependencyProperty.Register(
                "FieldTappedCommand", typeof(ICommand), typeof(Board2DControl),
                new PropertyMetadata(null));
        }
        
        /// <summary>
        /// Constructor
        /// </summary>
        public Board2DControl()
        {
            _figureMap = new Dictionary<Image, Position>();
            _markedFields = new Dictionary<Border,Position>();
            
        }

        /// <summary>
        /// Edit Mode Property CLR wrapper
        /// </summary>
        public bool EditMode
        {
            get
            {
                return (bool)GetValue(EditModeProperty);
            }
            set
            {
                SetValue(EditModeProperty, value);
            }
        }

        /// <summary>
        /// Field tapped command CLR property wrapper
        /// </summary>
        public ICommand FieldTappedCommand
        {
            get
            {
                return (ICommand)GetValue(FieldTappedCommandProperty);
            }
            set
            {
                SetValue(FieldTappedCommandProperty, value);
            }
        }


        /// <summary>
        /// Called after dependencies has been resolved
        /// </summary>
        public void OnInitialized()
        {
            // build the internals of this control
            Build();
        }

        /// <summary>
        /// Builds the panel
        /// </summary>
        private void Build()
        {
            // create and attach positioning panel
            _uxPanel = new Canvas();
            _uxPanel.Width = _boardSize;
            _uxPanel.Height = _boardSize;
            this.Content = _uxPanel;

            // TO-DO: make better selection of style
            _style = StyleManager.GetStyle(eGameTheme.Classic);

            // load images
            LoadImages();

            // creates the content of the panel
            SetupBoardImageControl();
        }

        /// <summary>
        /// Creates the content of the board
        /// </summary>
        public void SetupBoardImageControl()
        {
            _uxPanel.Children.Clear();
            _figureMap.Clear();

            // TO-DO: react to the reversed attribute
            // TO-DO: think of animation of rotation when reversed triggered
            _boardImageControl = new Image();
            _boardImageControl.Source = _board;
            Canvas.SetLeft(_boardImageControl, 0);
            Canvas.SetTop(_boardImageControl, 0);
            Canvas.SetZIndex(_boardImageControl, 0);
            _uxPanel.Children.Add(_boardImageControl);

            // place figure images
            for(int row = 0; row < GameBoard.Size; row++)
            {
                for(int col = 0; col < GameBoard.Size; col++)
                {
                    // get figure at given field
                    Position pos = new Position(row, col);
                    enumFigureType figType = GameBoard.GetField(pos);

                    // get image
                    BitmapSource imageSource = GetPieceImage(figType);
                    if(imageSource != null)
                    {
                        // create image
                        Image pieceImage = new Image();
                        pieceImage.IsHitTestVisible = true;
                        pieceImage.Source = imageSource;

                        // get the position
                        Rect fieldRect;
                        GetFieldRect(pos, out fieldRect);
                        pieceImage.Width = fieldRect.Width;
                        pieceImage.Height = fieldRect.Height;

                        // place image
                        Canvas.SetLeft(pieceImage, fieldRect.Left);
                        Canvas.SetTop(pieceImage, fieldRect.Top);
                        Canvas.SetZIndex(pieceImage, 1);

                        _uxPanel.Children.Add(pieceImage);
                        _figureMap[pieceImage] = pos;
                    }
                }
            }
        }

        /// <summary>
        /// Returns image by figure type from gameboard
        /// </summary>
        private BitmapSource GetPieceImage(enumFigureType figureType)
        {
            switch(figureType)
            {
                case enumFigureType.WhiteMan:
                    return _whitePawn;
                case enumFigureType.BlackMan:
                    return _blackPawn;
                case enumFigureType.WhiteKing:
                    return _whiteKing;
                case enumFigureType.BlackKing:
                    return _blackKing;
                default:
                    return null;
            }
        }

        /// <summary>
        /// Loads all necessary images
        /// </summary>
        private void LoadImages()
        {
            _board = LoadImage(_style.BoardImage);
            _boardReversed = LoadImage(_style.BoardImageReversed);
            _whitePawn = LoadImage(_style.WhitePawnImage);
            _blackPawn = LoadImage(_style.BlackPawnImage);
            _whiteKing = LoadImage(_style.WhiteKingImage);
            _blackKing = LoadImage(_style.BlackKingImage);
        }

        /// <summary>
        /// Loads image with given name
        /// </summary>
        private BitmapImage LoadImage(string imageName)
        {
            string fileUri = _imagePath + imageName;
            return new BitmapImage(new Uri(fileUri));
        }

        /// <summary>
        /// Returns rect for drawing for given position
        /// </summary>
        private void GetFieldRect(Position pos, out Rect fieldRect)
        {
            // TO-DO: check reversed board
            // CheckCoordinatesRotated(ref pos);

            double fieldSize = (_style.MarginRight- _style.MarginLeft) / GameBoard.Size;

            // calculate the field
            double fieldX = pos.Col * fieldSize + _style.MarginLeft;
            double fieldY = (GameBoard.Size - pos.Row - 1) * fieldSize + _style.MarginTop;

            fieldRect = new Rect(fieldX, fieldY, fieldSize, fieldSize);
        }

        /// <summary>
        /// Returns gameboard field by given mouse coordinates
        /// </summary>
        private void GetFieldByPoint(Point point, out Position pos)
        {
            pos = Position.Empty;

            double fieldSize = (_style.MarginRight - _style.MarginLeft) / GameBoard.Size;

            double x = point.X - _style.MarginLeft;
            double y = point.Y - _style.MarginTop;

            if (x < 0 || y < 0)
                return;

            x = x / fieldSize;
            y = y / fieldSize;

            int col = (int)Math.Floor(x);
            int row = (int)GameBoard.Size - (int)Math.Floor(y) - 1;

            if (row >= GameBoard.Size || col >= GameBoard.Size)
                return;

            pos = new Position(row, col);

            // TO-DO: check reversed board
            // CheckCoordinatesRotated(ref pos);
        }

        /// <summary>
        /// Finger or mouse tapped handlers
        /// </summary>
        protected override void OnTapped(Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            // TO-DO: refactor and simplify
            if (EditMode == true)
            {
                // get the mouse position
                Point mousePoint = e.GetPosition(_boardImageControl);

                // get the field
                Position fieldPos;
                GetFieldByPoint(mousePoint, out fieldPos);

                // check if not outside bounds
                if(fieldPos != Position.Empty)
                {
                    // check, if command not null and if can execute
                    if(FieldTappedCommand != null && FieldTappedCommand.CanExecute(fieldPos))
                    {
                        // call the command handler
                        FieldTappedCommand.Execute(fieldPos);
                    }
                }
            }
            else
            {
                if (!(e.OriginalSource is Image || e.OriginalSource is Border))
                    return;

                if (_moveInProgress == false)
                {
                    if (e.OriginalSource is Border)
                        return;

                    // try to get figure
                    Position pos;
                    if (_figureMap.TryGetValue(e.OriginalSource as Image, out pos) == false)
                        return;

                    // check, if given figure can move
                    if (UserMoveEntering.CanFigureMove(pos) == false)
                        return;

                    // get the list of target fields
                    List<Position> targetFields = UserMoveEntering.GetFigureTargetFields(pos);

                    // mark all fields
                    foreach (Position target in targetFields)
                    {
                        MarkField(target);
                    }

                    _moveStartPos = pos;
                    _movingImage = e.OriginalSource as Image;

                    _moveInProgress = true;
                }
                else
                {
                    if (e.OriginalSource is Border)
                    {
                        Position targetPos;
                        if (_markedFields.TryGetValue(e.OriginalSource as Border, out targetPos))
                        {
                            // make the move
                            UserMoveEntering.DoMoveStep(_moveStartPos, targetPos);

                            // clear marks
                            ClearFieldMarks();

                            _targetPos = targetPos;

                            // create animation
                            // TO-DO: remove handler from animation
                            Storyboard animation = CreateMoveAnimation(_movingImage, _moveStartPos, targetPos, UserMoveEntering.IsLastMoveStepCapturing());
                            animation.Completed += animation_Completed;
                            animation.Begin();
                        }
                    }
                    else if (_moveInProgress)
                    {
                        // clear marks
                        ClearFieldMarks();

                        _moveInProgress = false;
                    }
                }
            }

            base.OnTapped(e);
        }

        void animation_Completed(object sender, object e)
        {
            // TO-DO: make better board refresh
            SetupBoardImageControl();

            // check move continuation
            if (UserMoveEntering.DoesMoveContinue())
            {
                List<Position> targetFields = UserMoveEntering.GetFigureTargetFields(_targetPos);

                // mark all fields
                foreach (Position target in targetFields)
                {
                    MarkField(target);
                }

                _moveStartPos = _targetPos;
            }
            else
            {
                UserMoveEntering.FinalizeMove();
                _moveInProgress = false;
            }
        }


        /// <summary>
        /// Clears marked fields
        /// </summary>
        private void ClearFieldMarks()
        {
            foreach(var border in _markedFields.Keys)
            {
                _uxPanel.Children.Remove(border);
            }

            _markedFields.Clear();
        }

        
        /// <summary>
        /// Puts a mark onto a field
        /// </summary>
        private void MarkField(Position pos)
        {
            // get field rect
            Rect rect;
            GetFieldRect(pos, out rect);

            // create marking element
            Border border = new Border();
            border.Width = rect.Width;
            border.Height = rect.Height;
            border.CornerRadius = new Windows.UI.Xaml.CornerRadius(5d);

            // set background
            LinearGradientBrush brush = new LinearGradientBrush();
            GradientStop gr = new GradientStop();
            gr.Color = Colors.Yellow;
            gr.Offset = 0;
            brush.GradientStops.Add(gr);
            gr = new GradientStop();
            gr.Color = Colors.Orange;
            gr.Offset = 1;
            brush.GradientStops.Add(gr);

            border.Background = brush;
            border.Opacity = 0.5d;

            // place element
            Canvas.SetLeft(border, rect.Left);
            Canvas.SetTop(border, rect.Top);
            Canvas.SetZIndex(border, 2);

            // add to the container
            _uxPanel.Children.Add(border);
            _markedFields[border] = pos;
        }

        /// <summary>
        /// Returns figure image by given position
        /// </summary>
        Image GetFigureImageByField(Position pos)
        {
            foreach(var pair in _figureMap)
            {
                if (pair.Value == pos)
                    return pair.Key;
            }

            return null;
        }

        private Storyboard CreateMoveAnimation(Image image, Position srcField, Position targetField, Position? captureField)
        {
            Storyboard sb = new Storyboard();

            // put animated image over other images
            Canvas.SetZIndex(image, 3);
            
            // set animation duration
            double len = Math.Abs(srcField.Row - targetField.Row);
            const double maxDurationLength = 1.5d;

            // calculate easing function
            const double power = 2d;
            const double boardSize = 8d;

            double a = (maxDurationLength - boardSize) / (Math.Pow(boardSize, power) - boardSize);
            double b = 1d - a;
            len = a * Math.Pow(len, power) + b * len;
            const double durationStep = 450d;
          

            // get field rect
            Rect rect;
            GetFieldRect(targetField, out rect);

            // create X-coordinate animation 
            DoubleAnimation dblAnimX = new DoubleAnimation();            
            dblAnimX.To = rect.Left;
            dblAnimX.Duration = new Duration(TimeSpan.FromMilliseconds(len * durationStep));
            dblAnimX.EasingFunction = new QuadraticEase();

            Storyboard.SetTarget(dblAnimX, image);
            Storyboard.SetTargetProperty(dblAnimX, "(Canvas.Left)");

            sb.Children.Add(dblAnimX);

            // create Y-coordinate animation 
            DoubleAnimation dblAnimY = new DoubleAnimation();
            dblAnimY.To = rect.Top;
            dblAnimY.Duration = new Duration(TimeSpan.FromMilliseconds(len * durationStep));
            dblAnimY.EasingFunction = new QuadraticEase();

            Storyboard.SetTarget(dblAnimY, image);
            Storyboard.SetTargetProperty(dblAnimY, "(Canvas.Top)");

            sb.Children.Add(dblAnimY);         

            // capturing piece animation
            if(captureField.HasValue)
            {
                Image captureImage = GetFigureImageByField(captureField.Value);
                if(captureImage != null)
                {
                    // create capture figure animation - slowly to disappear
                    DoubleAnimation dblCapture = new DoubleAnimation();
                    dblCapture.To = 0d;
                    dblCapture.Duration = new Duration(TimeSpan.FromMilliseconds(len * durationStep));
                    Storyboard.SetTarget(dblCapture, captureImage);
                    Storyboard.SetTargetProperty(dblCapture, "Opacity");

                    sb.Children.Add(dblCapture);
                }
            }

            return sb;
        }
    }
}
